#include <GeomAPI_PointsToBSpline.hxx>
#include <Geom_BSplineCurve.hxx>

#include "Qt3DPicker.h"
#include "FitCurve2DHandler.h"
#include "CmdManager.h"
#include "Scene.h"
#include "SendPointHandlerBase.h"
#include "SelectHandler3D.h"
#include "OSGNodeMask.h"
#include "Operation/OpReplace.h"
#include "Operation/OpAdd.h"
#include "Spline2D.h"
#include "PointHandler.h"
#include "LineStrip2D.h"
#include "FitCurveTool.h"
#include "QtOSGWidget.h"

std::vector<osg::Vec3d> convert(const TColgp_Array1OfPnt& points);
TColgp_Array1OfPnt convert(const std::vector<osg::Vec3d>& points);
std::vector<double> convert(const TColStd_Array1OfReal& weights);
std::vector<int> convert(const TColStd_Array1OfInteger& mults);
void toACADKnots(const std::vector<double>& knots, const std::vector<int>& mults, std::vector<double>& acadKnots);

std::shared_ptr<Spline2D> makeFitCurve(INode* parent, const std::vector<osg::Vec3d>& points)
{
	try
	{
		GeomAPI_PointsToBSpline approx(convert(points), 2, 3, GeomAbs_C2, 0.002);
		Handle(Geom_BSplineCurve) curve = approx.Curve();

		auto spline = std::make_shared<Spline2D>(parent);

		int degree = curve->Degree();
		spline->setDegree(degree);

		int flag = curve->IsClosed() ? 1 : 0;
		flag |= curve->IsPeriodic() ? 2 : 0;
		spline->setFlag(flag | 8);

		const TColgp_Array1OfPnt& poles = curve->Poles();
		std::vector<osg::Vec3d> polesV = convert(poles);
		spline->setCtrlPoints(polesV);

		const TColStd_Array1OfReal* weights = curve->Weights();
		if (weights)
		{
			std::vector<double> weightsV = convert(*weights);
			spline->setWeights(weightsV);
		}

		auto knotDist = curve->KnotDistribution();
		std::vector<double> acadKnots;
		if (knotDist == GeomAbs_PiecewiseBezier)
		{
			Standard_Integer PNum = poles.Size();
			Standard_Integer KNum = PNum - degree + 1;
			TColStd_Array1OfReal knots(1, KNum);
			for (int i = 0; i < KNum; ++i)
			{
				knots.SetValue(i + 1, i);
			}
			TColStd_Array1OfInteger mults(1, KNum);
			for (int i = 0; i < KNum; ++i)
			{
				if (i == 0 || i == KNum - 1)
				{
					mults.SetValue(i + 1, degree + 1);
				}
				else
				{
					mults.SetValue(i + 1, degree);
				}
			}
			toACADKnots(convert(knots), convert(mults), acadKnots);
		}
		else
		{
			const TColStd_Array1OfReal& knots = curve->Knots();
			const TColStd_Array1OfInteger& mults = curve->Multiplicities();
			toACADKnots(convert(knots), convert(mults), acadKnots);
		}
		spline->setKnots(acadKnots);
		return spline;
	}
	catch (Standard_Failure&)
	{
		return nullptr;
	}
}

FitCurve2DHandler::FitCurve2DHandler()
{
}

FitCurve2DHandler::~FitCurve2DHandler()
{
}

void FitCurve2DHandler::input(const QVariant& data, const QString& rawInput, CommandSource cs)
{
	auto scene = getScene();
	auto ucs = scene->getCurrentUCS();
	auto sph = getSendPointHandler();
	auto selector = qobject_cast<SelectHandler3D*>(getSelectHandler());
	if (data.type() == QVariant::Bool)
	{
		bool esc = !data.toBool();
		if (esc)
		{
			selector->clearSelection();
			clearInput();
			emit finished();
			return;
		}
	}

	if (status() == 0)
	{
		if (selector)
		{
			selector->clearSelection();
			selector->beginSelect(D2LineMask);
			prompt(1, tr("选择连续的线段: "));
		}
		else
		{
			emit finished();
		}
		return;
	}

	if (status() == 1)
	{
		if (selector && selector->done())
		{
			auto selections = selector->selectionSet();
			if (selections.size() > 1)
			{
				std::shared_ptr<Ent> ent = std::dynamic_pointer_cast<Ent>(*selections.begin());
				auto parent = ent->getParent();
				UCS* ucs = dynamic_cast<UCS*>(parent);
				if (ucs)
				{
					m_ucs = ucs;
				}
				else
				{
					remark(tr("请选择二维图形"));
					emit finished();
					return;
				}
				std::vector<CurvePoints> curvePoints;
				for (auto selection : selections)
				{
					auto ent = std::dynamic_pointer_cast<LineStrip2D>(selection);
					if (ent)
					{
						auto parent = ent->getParent();
						UCS* ucs = dynamic_cast<UCS*>(parent);
						if (ucs != m_ucs)
						{
							remark(tr("请选择同一平面内的直线"));
							emit finished();
						}
						auto points = ent->getPoints();
						if (points.size() >= 2)
						{
							curvePoints.push_back({ ent, points });
						}
					}
				}
				selector->clearSelection();
				std::vector<osg::Vec3d> points = connectEnds(curvePoints, m_usedEnts);
				if (points.size() >= 3 && m_usedEnts.size() > 1)
				{
					m_spline = makeFitCurve(m_ucs, points);
					m_spline->setColor(ucs->getUCSColor());
					if (m_spline)
					{
						enableMouseHandler(MouseHandlerID::Manipulator);
						prompt(2, tr("删除原来的对象[是(y)/否(n)]<y>: "));
						return;
					}
					else
					{
						remark(tr("拟合样条线失败"));
					}
				}
				else
				{
					remark(tr("拟合样条线失败"));
				}
				emit finished();
			}
		}
		return;
	}

	if (status() == 2)
	{
		auto undoStack = scene->undoStack();
		if (rawInput == "Y" || rawInput == "y" || (data.type() == QVariant::Bool && data.toBool()))
		{
			// delete original objects
			std::vector<std::shared_ptr<Ent>> newEnts;
			newEnts.push_back(m_spline);
			undoStack->push(new OpReplace(std::vector<std::shared_ptr<Ent>>(m_usedEnts.begin(), m_usedEnts.end()), newEnts));
		}
		else
		{
			std::set<std::shared_ptr<INode>> newEnts2;
			newEnts2.insert(m_spline);
			undoStack->push(new OpAdd(newEnts2));
		}
		emit finished();
		return;
	}
}


FitPoints2DHandler::FitPoints2DHandler()
{
}

FitPoints2DHandler::~FitPoints2DHandler()
{
}

void FitPoints2DHandler::input(const QVariant& data, const QString& rawInput, CommandSource cs)
{
	auto scene = getScene();
	auto widget3D = getWidget();
	auto ucs = scene->getCurrentUCS();
	auto sph = getSendPointHandler();
	if (data.type() == QVariant::Bool)
	{
		bool esc = !data.toBool();
		if (esc)
		{
			if (m_line)
			{
				widget3D->pauseRenderThread();
				ucs->removeChild(m_line);
				widget3D->resumeRenderThread();
			}
			clearInput();
			emit finished();
			return;
		}
	}

	if (status() == 0)
	{
		if (sph)
		{
			sph->setPointMask(CS_LBTNDOWN);
		}
		m_line = std::make_shared<LineStrip2D>(ucs.get());
		m_line->setColor(ucs->getUCSColor());
		widget3D->pauseRenderThread();
		ucs->addChild(m_line);
		widget3D->resumeRenderThread();
		cmdManager()->setPlane((Plane2DKind)ucs->getKind());
		enableMouseHandler(MouseHandlerID::PointSender);
		prompt(1, tr("指定曲线拟合点: "));
		return;
	}

	std::vector<osg::Vec3d> points = m_line->getPoints();
	if (status() == 1)
	{
		if (data.type() == QVariant::Bool && data.toBool())
		{
			widget3D->pauseRenderThread();
			ucs->removeChild(m_line);
			widget3D->resumeRenderThread();
			emit finished();
			return;
		}
		if (sph)
		{
			sph->setPointMask(CS_LBTNDOWN | CS_MOUSEMOVE);
		}
		osg::Vec3d ucspt;
		if (getPoint2D(*ucs, data, rawInput, cs, ucspt))
		{
			points.push_back(ucspt);
			points.push_back(ucspt);
			widget3D->pauseRenderThread();
			m_line->setPoints(points);
			widget3D->resumeRenderThread();
			prompt(2, tr("指定曲线拟合点: "));
		}
		return;
	}

	if (status() >= 2)
	{
		if (data.type() == QVariant::Bool && data.toBool())
		{
			widget3D->pauseRenderThread();
			ucs->removeChild(m_line);
			widget3D->resumeRenderThread();
			if (status() <= 3)
			{
				// 只有两个点，不能拟合曲线
				emit finished();
				return;
			}
			points.pop_back();
			widget3D->pauseRenderThread();
			m_line->setPoints(points);
			widget3D->resumeRenderThread();
			makeSpline(scene);
			emit finished();
			return;
		}

		osg::Vec3d ucspt;
		if (getPoint2D(*ucs, data, rawInput, cs, ucspt))
		{
			if (cs == CS_MOUSEMOVE)
			{
				points[points.size() - 1] = ucspt;
				m_line->setPoints(points);
			}
			else
			{
				points.pop_back();
				points.push_back(ucspt);
				points.push_back(ucspt);
				widget3D->pauseRenderThread();
				m_line->setPoints(points);
				widget3D->resumeRenderThread();
				prompt(status() + 1, tr("指定曲线拟合点: "));
			}
		}
		return;
	}
}

void FitPoints2DHandler::makeSpline(Scene* scene)
{
	std::vector<osg::Vec3d> points = m_line->getPoints();
	auto ucs = scene->getCurrentUCS();
	m_spline = makeFitCurve(ucs.get(), points);
	m_spline->setColor(ucs->getUCSColor());
	if (m_spline)
	{
		std::set<std::shared_ptr<INode>> newEnts2;
		newEnts2.insert(m_spline);
		auto undoStack = scene->undoStack();
		undoStack->push(new OpAdd(newEnts2));
		return;
	}
	else
	{
		remark(tr("拟合样条线失败"));
	}
}
